/*
 * Copyright (c) JForum Team
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: 05/04/2004 - 20:11:44
 * The JForum Project
 * http://www.jforum.net
 */
package net.jforum.repository;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.jforum.cache.CacheEngine;
import net.jforum.cache.Cacheable;
import net.jforum.dao.DataAccessDriver;
import net.jforum.dao.TopicDAO;
import net.jforum.entities.Topic;
import net.jforum.entities.TopicTypeComparator;
import net.jforum.util.preferences.ConfigKeys;
import net.jforum.util.preferences.SystemGlobals;

/**
 * Repository for the last n topics for each forum.
 * 
 * @author Rafael Steil
 * @author James Yong
 * @version $Id: TopicRepository.java,v 1.33 2007/09/05 04:00:27 rafaelsteil Exp
 *          $
 */
public class TopicRepository implements Cacheable {
    private static int maxItems = SystemGlobals
	    .getIntValue(ConfigKeys.TOPICS_PER_PAGE);

    private static final String FQN = "topics";
    private static final String FQNR = "topicRepository";
    private static final String RECENT = "recent";
    private static final String HOTTEST = "hottest";
    private static final String FQN_FORUM = FQN + "/byforum";
    private static final String RELATION = "relation";
    private static final String FQN_LOADED = FQN + "/loaded";
    private static final Comparator TYPE_COMPARATOR = new TopicTypeComparator();

    private static CacheEngine cache;

    /**
     * @see net.jforum.cache.Cacheable#setCacheEngine(net.jforum.cache.CacheEngine)
     */
    public void setCacheEngine(CacheEngine engine) {
	cache = engine;
    }

    public static boolean isLoaded(int forumId) {
	return "1".equals(cache.get(FQN_LOADED, Integer.toString(forumId)));
    }

    /**
     * Add topic to the FIFO stack
     * 
     * @param topic
     *            The topic to add to stack
     */
    public synchronized static void pushTopic(Topic topic) {
	if (SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    int limit = SystemGlobals.getIntValue(ConfigKeys.RECENT_TOPICS);

	    LinkedList l = (LinkedList) cache.get(FQN, RECENT);
	    if (l == null || l.size() == 0) {
		l = new LinkedList(loadMostRecentTopics());
	    }

	    l.remove(topic);
	    l.addFirst(topic);

	    while (l.size() > limit) {
		l.removeLast();
	    }

	    cache.add(FQN, RECENT, l);
	}
    }

    /**
     * Get all cached recent topics.
     * 
     */
    public static List getRecentTopics() {
	List l = (List) cache.get(FQN, RECENT);

	if (l == null || l.size() == 0
		|| !SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    l = loadMostRecentTopics();
	}

	return new ArrayList(l);
    }

    /**
     * Get all cached hottest topics.
     * 
     */
    public static List getHottestTopics() {
	List l = (List) cache.get(FQN, HOTTEST);

	if (l == null || l.size() == 0
		|| !SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    l = loadHottestTopics();
	}

	return new ArrayList(l);
    }

    /**
     * Add recent topics to the cache
     */
    public synchronized static List loadMostRecentTopics() {
	TopicDAO tm = DataAccessDriver.getInstance().newTopicDAO();
	int limit = SystemGlobals.getIntValue(ConfigKeys.RECENT_TOPICS);

	List l = tm.selectRecentTopics(limit);
	cache.add(FQN, RECENT, new LinkedList(l));

	return l;
    }

    /**
     * Add hottest topics to the cache
     */
    public synchronized static List loadHottestTopics() {
	TopicDAO tm = DataAccessDriver.getInstance().newTopicDAO();
	int limit = SystemGlobals.getIntValue(ConfigKeys.HOTTEST_TOPICS);

	List l = tm.selectHottestTopics(limit);
	cache.add(FQN, HOTTEST, new LinkedList(l));

	return l;
    }

    /**
     * Add topics to the cache
     * 
     * @param forumId
     *            The forum id to which the topics are related
     * @param topics
     *            The topics to add
     */
    public static void addAll(int forumId, List topics) {
	if (SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    synchronized (FQN_FORUM) {
		cache.add(FQN_FORUM, Integer.toString(forumId), new LinkedList(
			topics));

		Map m = (Map) cache.get(FQN, RELATION);

		if (m == null) {
		    m = new HashMap();
		}

		Integer fId = new Integer(forumId);

		for (Iterator iter = topics.iterator(); iter.hasNext();) {
		    Topic t = (Topic) iter.next();

		    m.put(new Integer(t.getId()), fId);
		}

		cache.add(FQN, RELATION, m);
		cache.add(FQN_LOADED, Integer.toString(forumId), "1");
	    }
	}
    }

    /**
     * Clears the cache
     * 
     * @param forumId
     *            The forum id to clear the cache
     */
    public static void clearCache(int forumId) {
	synchronized (FQN_FORUM) {
	    cache.add(FQN_FORUM, Integer.toString(forumId), new LinkedList());
	    cache.remove(FQN, RELATION);
	}
    }

    /**
     * Adds a new topic to the cache
     * 
     * @param topic
     *            The topic to add
     */
    public static void addTopic(Topic topic) {
	if (!SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    return;
	}

	synchronized (FQN_FORUM) {
	    String forumId = Integer.toString(topic.getForumId());
	    LinkedList list = (LinkedList) cache.get(FQN_FORUM, forumId);

	    if (list == null) {
		list = new LinkedList();
		list.add(topic);
	    } else {
		boolean contains = list.contains(topic);

		// If the cache is full, remove the eldest element
		if (!contains && list.size() + 1 > maxItems) {
		    list.removeLast();
		} else if (contains) {
		    list.remove(topic);
		}

		list.add(topic);

		Collections.sort(list, TYPE_COMPARATOR);
	    }

	    cache.add(FQN_FORUM, forumId, list);

	    Map m = (Map) cache.get(FQN, RELATION);

	    if (m == null) {
		m = new HashMap();
	    }

	    m.put(new Integer(topic.getId()), new Integer(forumId));

	    cache.add(FQN, RELATION, m);
	}
    }

    /**
     * Updates a cached topic
     * 
     * @param topic
     *            The topic to update
     */
    public static void updateTopic(Topic topic) {
	if (SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    synchronized (FQN_FORUM) {
		String forumId = Integer.toString(topic.getForumId());
		List l = (List) cache.get(FQN_FORUM, forumId);

		if (l != null) {
		    int index = l.indexOf(topic);

		    if (index > -1) {
			l.set(index, topic);
			cache.add(FQN_FORUM, forumId, l);
		    }
		}
	    }
	}
    }

    /**
     * Gets a cached topic.
     * 
     * @param t
     *            The topic to try to get from the cache. The instance passed as
     *            argument should have ae least the topicId and forumId set
     * @return The topic instance, if found, or <code>null</code> otherwise.
     */
    public static Topic getTopic(Topic t) {
	if (!SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    return null;
	}

	if (t.getForumId() == 0) {
	    Map m = (Map) cache.get(FQN, RELATION);

	    if (m != null) {
		Integer forumId = (Integer) m.get(new Integer(t.getId()));

		if (forumId != null) {
		    t.setForumId(forumId.intValue());
		}
	    }

	    if (t.getForumId() == 0) {
		return null;
	    }
	}

	List l = (List) cache.get(FQN_FORUM, Integer.toString(t.getForumId()));

	int index = -1;

	if (l != null) {
	    index = l.indexOf(t);
	}

	return (index == -1 ? null : (Topic) l.get(index));
    }

    public static Topic getTopic(int id) {
	TopicDAO tm = DataAccessDriver.getInstance().newTopicDAO();

	return tm.selectById(id);
    }

    /**
     * Checks if a topic is cached
     * 
     * @param topic
     *            The topic to verify
     * @return <code>true</code> if the topic is cached, or <code>false</code>
     *         if not.
     */
    public static boolean isTopicCached(Topic topic) {
	if (!SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    return false;
	}

	String forumId = Integer.toString(topic.getForumId());
	List list = (List) cache.get(FQN_FORUM, forumId);

	return list == null ? false : list.contains(topic);
    }

    /**
     * Get all cached topics related to a forum.
     * 
     * @param forumid
     *            The forum id
     * @return <code>ArrayList</code> with the topics.
     */
    public static List getTopics(int forumid) {
	if (SystemGlobals.getBoolValue(ConfigKeys.TOPIC_CACHE_ENABLED)) {
	    synchronized (FQN_FORUM) {
		List returnList = (List) cache.get(FQN_FORUM,
			Integer.toString(forumid));

		if (returnList == null) {
		    return new ArrayList();
		}

		return new ArrayList(returnList);
	    }
	}

	return new ArrayList();
    }
}
